package errors

import (
	"encoding/json"
	"errors"
	"fmt"
	"runtime"
)

var (
	New    = errors.New
	Is     = errors.Is
	As     = errors.As
	Unwrap = errors.Unwrap
)

const (
	Silent       = "0"
	MessageWarn  = "1"
	MessageError = "2"
	Notification = "4"
	Page         = "9"
)

// UnsupportedMethod 如果某个方法禁止被调用则可以抛出此错误。
var UnsupportedMethod = errors.New("unsupported method")

// UnimplementedMethod 如果某个方法未实现则可以抛出此错误。
var UnimplementedMethod = errors.New("unimplemented method")


type Error struct {
	ErrorCode    string
	ErrorMessage string
	ShowType     string
	TraceId      string
	Domain       string
}

// ErrorCoder error code
type ErrorCoder interface {
	String() string
	Code() int32
}

// New generates a custom error.
func NewError(id, domain string, code ErrorCoder) error {
	return &Error{
		ErrorCode:    fmt.Sprintf("C%d", code.Code()),
		ErrorMessage: code.String(),
		ShowType:     MessageError,
		TraceId:      id,
		Domain:       domain,
	}
}

func (e *Error) Error() string {
	b, _ := json.Marshal(e)
	return string(b)
}

// ToString 返回 error 的字符串。
func ToString(err error) string {
	if err == nil {
		return "<nil>"
	}
	return err.Error()
}

type withCause struct {
	cause interface{}
}

// WithCause 封装一个异常源。
func WithCause(r interface{}) error {
	return &withCause{cause: r}
}

func (c *withCause) Error() string {
	return fmt.Sprint(c.cause)
}

// Cause 获取封装的异常源。
func Cause(err error) interface{} {
	if c, ok := err.(*withCause); ok {
		return c.cause
	}
	return err
}

// WithFileLine 返回错误发生的文件行号，skip 是相对于当前函数的深度。
func WithFileLine(err error, skip int) error {
	_, file, line, _ := runtime.Caller(skip + 1)
	str := fmt.Sprintf("%s:%d", file, line)
	if err != nil {
		str += ": " + err.Error()
	}
	return New(str)
}

// Parse tries to parse a JSON string into an error. If that
// fails, it will set the given string as the error detail.
func Parse(errStr string) *Error {
	e := new(Error)
	err := json.Unmarshal([]byte(errStr), e)
	if err != nil {
		e.ErrorMessage = errStr
	}
	return e
}

// Equal tries to compare errors
func Equal(err1 error, err2 error) bool {
	verr1, ok1 := err1.(*Error)
	verr2, ok2 := err2.(*Error)

	if ok1 != ok2 {
		return false
	}

	if !ok1 {
		return err1 == err2
	}

	if verr1.ErrorCode != verr2.ErrorCode {
		return false
	}

	return true
}

// FromError try to convert go error to *Error
func FromError(err error) *Error {
	if verr, ok := err.(*Error); ok && verr != nil {
		return verr
	}
	return Parse(err.Error())
}
